package com.fury.framework;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.fury.managers.EnvironmentManager;
import com.fury.managers.StateManager;

public class FuryEngine {
	// TODO: shall we use log4J as ou logger?
	private static String MANAGER_STR = "manager";
	private static String SYSTEM_STR = "system";
	
	Scheduler scheduler;
	boolean gameLoopEnded;
	
	// Manager handles
	
	
	public void start(String configFile) throws FuryException{
		
		initialization(configFile);
		
		gameLoop();
	}
	
	private void initialization(String configFile) throws FuryException{
		scheduler = new Scheduler();
		
		gameLoopEnded = false;
		
		// load framework config file
		Document doc = loadConfigDocument(configFile);
		
		// start configuration from XML file
		configureEngineFromDocument(doc);
	}
	
	private void configureEngineFromDocument(Document doc) throws FuryException {
		
		// Systems
		configureSystems(doc);
		
		// Managers
		configureManagers(doc);
	}

	private void configureManagers( Document doc ) throws FuryException {
		List<ObjectConfiguration> managers = getObjectConfigurations(doc, MANAGER_STR);
		
		for( ObjectConfiguration manager : managers ){
			// Environment Manager
			if ( manager.type.equalsIgnoreCase("EnvironmentManager") ){
				manager.properties.setProperty("class", manager.className);
				
				EnvironmentManager.getInstance().startEnvironment(manager.properties);
			}
		}
	}

	private void configureSystems( Document doc ) throws FuryException {
	   	
		List<ObjectConfiguration> systems = getObjectConfigurations(doc, SYSTEM_STR);
		
		for(ObjectConfiguration configuration : systems){
		   	 // Initialize the class
		   	 FurySystem system = null;
		   	 
		   	 try {
					system = (FurySystem) Class.forName( configuration.className ).newInstance();
		   	 } catch (ClassNotFoundException cnfEx) {
					// TODO: log the exception
					throw new FuryException("System class [" + configuration.className + "] was not found", cnfEx);
		   	 } catch (InstantiationException iEx) {
					// TODO: log the exception
					throw new FuryException("System class [" + configuration.className + "] could not be instanciated", iEx);
		   	 } catch (IllegalAccessException iaEx) {
					// TODO: log the exception
					throw new FuryException("Illegal access to [" + configuration.className + "]", iaEx);
		   	 }
		   	 
		   	 // initialize the system
		   	 system.initialize(configuration.properties);
		   	 
		   	 // add it to the scheduler
		   	 scheduler.addSystem(system);
	   	 }
	}

	private List<ObjectConfiguration> getObjectConfigurations(Document doc, String object) {
		List<ObjectConfiguration> confList = new LinkedList<ObjectConfiguration>();
		
		NodeList systemList = doc.getElementsByTagName(object);
        
        for(int objectIdx = 0; objectIdx < systemList.getLength() ; objectIdx++){
        	 Node node = systemList.item(objectIdx);
        	 
        	 if(node.getNodeType() == Node.ELEMENT_NODE){
        		 ObjectConfiguration conf = new ObjectConfiguration();
            	 
            	 Element systemElement = (Element)node;
            	 // className
            	 
            	 conf.type = systemElement.getAttribute("type");
            	 NodeList classElementList = systemElement.getElementsByTagName("class");
            	 Element classElement = (Element)classElementList.item(0);
            	 conf.className = classElement.getTextContent();

            	 Properties properties = new Properties(); // TODO: I really dont like to use properties class because it loads the whole IO libs, but these are already loaded because of the XML parsing.
            	 // parameter list
            	 NodeList paramsElementList = systemElement.getElementsByTagName("param");
            	 
            	 for(int paramIdx = 0; paramIdx < paramsElementList.getLength(); paramIdx++){
            		 Node paramNode = paramsElementList.item(paramIdx);
            		 
            		 if(node.getNodeType() == Node.ELEMENT_NODE){
            			 Element paramElement = (Element)paramNode;
            			 
            			 properties.put(paramElement.getAttribute("name"), paramElement.getTextContent());
            		 }
            	 }
            	 
            	 conf.properties = properties;
            	 
            	 confList.add(conf);
             }

        }
        
        return confList;
	}

	private void gameLoop() throws FuryException{
		while(!gameLoopEnded){
			// TODO: Process Window Messages
			
			// TODO: Scheduler Execution
			scheduler.tick();
			// TODO: Distribute Changes

			
			// TODO: Check Execution Status
			gameLoopEnded = EnvironmentManager.getInstance().isClosedRequested();
			System.out.println("> " + gameLoopEnded);
		}
		
		EnvironmentManager.getInstance().stopEnvironment();
	}
	
	private Document loadConfigDocument(String configFile) throws FuryException {
		
		// TODO: I am the first to hate XML, and I admit I that I can count the times in my life I used XML as a config file, but since we're trying to make a open source engine its probably better to use the de facto standard "XML"
		DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = null;
		try {
			docBuilder = docBuilderFactory.newDocumentBuilder();
		} catch (ParserConfigurationException pcEx) {
			// TODO: log the exception
			throw new FuryException(pcEx.getLocalizedMessage(), pcEx);
		}
		
		Document doc = null;
		try {
			doc = docBuilder.parse (new File(configFile));
		} catch (SAXException saxEx) {
			// TODO: log the exception
			throw new FuryException("Error parsing the configuration file :" + configFile, saxEx);
		} catch (IOException ioEx) {
			// TODO: log the exception
			throw new FuryException("Error opening configuration file : " + configFile, ioEx);
		}

        // normalize text representation
        doc.getDocumentElement ().normalize ();
        
        return doc;
	}

	
	private class ObjectConfiguration{
		public String className;
		public String type;
		public Properties properties; 
	}

}
